/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.interpreter.adapter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author Laurent WOUTERS
 */
public abstract class BaseProxyObject implements ProxyObject {
    private static final java.util.Random rand = new java.util.Random();
    
    protected static final List<Integer> activated = new ArrayList<Integer>();
    protected static final Map<String, java.lang.Class> mapIRI2Class = new HashMap<String, java.lang.Class>();
    protected static final Map<String, ProxyObjectGetter> mapIRI2Getter = new HashMap<String, ProxyObjectGetter>();
    protected static final BPODatabase<BaseProxyObject> database = new BPODatabase<BaseProxyObject>();
    
    protected xowl.interpreter.Repository repository;
    protected xowl.lang.runtime.Entity entity;
    protected xowl.lang.owl2.Ontology ontology;
    protected java.util.Map<ObjectPropertyObserver, ObjectPropertyObserverLink> mapObjObservers;
    protected java.util.Map<DataPropertyObserver, DataPropertyObserverLink> mapDataObservers;

    @Override public xowl.interpreter.Repository getRepository() { return repository; }
    @Override public xowl.lang.owl2.Ontology getOntology() { return ontology; }
    @Override public xowl.lang.runtime.Entity getEntity() { return entity; }
    @Override public xowl.lang.runtime.Individual getIndividual() { return repository.interpretAsIndividual(entity); }
    @Override public xowl.lang.owl2.IRI getIRI() { return entity.getHasIRI(); }
    @Override public String getIRIString() { return entity.getHasIRI().getHasValue(); }
    
    public BaseProxyObject(xowl.interpreter.Repository repository, xowl.lang.runtime.Entity entity) {
        this.repository = repository;
        this.entity = entity;
        this.ontology = entity.getContainedBy();
        this.mapObjObservers = new java.util.HashMap<ObjectPropertyObserver, ObjectPropertyObserverLink>();
        this.mapDataObservers = new java.util.HashMap<DataPropertyObserver, DataPropertyObserverLink>();
    }
    
    protected static String generateID() {
        StringBuilder builder = new StringBuilder();
        String value = Integer.toHexString(rand.nextInt()).toUpperCase();
        for (int i=0; i!=8-value.length(); i++)
            builder.append('0');
        builder.append(value);
        return builder.toString();
    }

    protected java.util.List<xowl.lang.runtime.Entity> getAllEntities(xowl.lang.owl2.IRI property) {
        java.util.List<xowl.lang.runtime.Entity> results = new java.util.ArrayList<xowl.lang.runtime.Entity>();
        xowl.lang.actions.QueryVariable var = new xowl.lang.actions.QueryVariable();
        var.setName("v");
        xowl.lang.actions.ValuesOf query = new xowl.lang.actions.ValuesOf();
        xowl.lang.owl2.ObjectPropertyAssertion axiom = new xowl.lang.owl2.ObjectPropertyAssertion();
        axiom.setObjectProperty(property);
        axiom.setIndividual(entity.getHasIRI());
        axiom.setValueIndividual(var);
        query.addAxioms(axiom);
        query.setOn(var);
        try {
            Collection<xowl.interpreter.QuerySolution> solutions = repository.select(query);
            for (xowl.interpreter.QuerySolution solution : solutions)
                results.add((xowl.lang.runtime.Entity)solution.getValue(var));
        } catch (xowl.interpreter.XOWLException ex) { }
        return results;
    }
    
    protected java.util.List<ProxyObject> getAllObjects(xowl.lang.owl2.IRI property) {
        xowl.lang.actions.QueryVariable vObject = new xowl.lang.actions.QueryVariable();
        vObject.setName("v");
        xowl.lang.actions.QueryVariable vClass = new xowl.lang.actions.QueryVariable();
        vClass.setName("c");
        
        xowl.lang.owl2.ObjectPropertyAssertion opa = new xowl.lang.owl2.ObjectPropertyAssertion();
        opa.setObjectProperty(property);
        opa.setIndividual(entity.getHasIRI());
        opa.setValueIndividual(vObject);
        
        xowl.lang.owl2.ClassAssertion ca = new xowl.lang.owl2.ClassAssertion();
        ca.setIndividual(vObject);
        ca.setClasse(vClass);
        
        xowl.lang.actions.SelectAll query = new xowl.lang.actions.SelectAll();
        query.addAxioms(opa);
        query.addAxioms(ca);
        
        List<ProxyObject> results = new ArrayList<ProxyObject>();
        List<xowl.lang.runtime.Value> entities = new ArrayList<xowl.lang.runtime.Value>();
        try {
            Collection<xowl.interpreter.QuerySolution> solutions = repository.select(query);
            for (xowl.interpreter.QuerySolution solution : solutions) {
                xowl.lang.runtime.Entity sObj = (xowl.lang.runtime.Entity)solution.getValue(vObject);
                xowl.lang.runtime.Entity sClass = (xowl.lang.runtime.Entity)solution.getValue(vClass);
                if (entities.contains(sObj))
                    continue;
                String iri = sClass.getHasIRI().getHasValue();
                ProxyObjectGetter getter = mapIRI2Getter.get(iri);
                if (getter == null)
                    continue;
                ProxyObject po = getter.get(repository, sObj);
                results.add(po);
                entities.add(sObj);
            }
        } catch (xowl.interpreter.XOWLException ex) { }
        return results;
    }

    protected java.util.List<Object> getAllData(xowl.lang.owl2.IRI property) {
        java.util.List<Object> results = new java.util.ArrayList<Object>();
        xowl.lang.actions.QueryVariable var = new xowl.lang.actions.QueryVariable();
        var.setName("v");
        xowl.lang.actions.ValuesOf query = new xowl.lang.actions.ValuesOf();
        xowl.lang.owl2.DataPropertyAssertion axiom = new xowl.lang.owl2.DataPropertyAssertion();
        axiom.setDataProperty(property);
        axiom.setIndividual(entity.getHasIRI());
        axiom.setValueLiteral(var);
        query.addAxioms(axiom);
        query.setOn(var);
        try {
            Collection<xowl.interpreter.QuerySolution> solutions = repository.select(query);
            for (xowl.interpreter.QuerySolution solution : solutions) {
                xowl.lang.runtime.Literal lit = (xowl.lang.runtime.Literal)solution.getValue(var);
                xowl.interpreter.impl.JavaDatatype datatype = xowl.interpreter.impl.JavaDatatype.get(lit.getMemberOf().getInterpretationOf().getHasIRI().getHasValue());
                results.add(datatype.getToValue(lit.getLexicalValue()));
            }
        } catch (xowl.interpreter.XOWLException ex) { }
        return results;
    }
    
    protected java.util.List<xowl.lang.runtime.Literal> getAllDataAsLiterals(xowl.lang.owl2.IRI property) {
        java.util.List<xowl.lang.runtime.Literal> results = new java.util.ArrayList<xowl.lang.runtime.Literal>();
        xowl.lang.actions.QueryVariable var = new xowl.lang.actions.QueryVariable();
        var.setName("v");
        xowl.lang.actions.ValuesOf query = new xowl.lang.actions.ValuesOf();
        xowl.lang.owl2.DataPropertyAssertion axiom = new xowl.lang.owl2.DataPropertyAssertion();
        axiom.setDataProperty(property);
        axiom.setIndividual(entity.getHasIRI());
        axiom.setValueLiteral(var);
        query.addAxioms(axiom);
        query.setOn(var);
        try {
            Collection<xowl.interpreter.QuerySolution> solutions = repository.select(query);
            for (xowl.interpreter.QuerySolution solution : solutions) {
                xowl.lang.runtime.Literal lit = (xowl.lang.runtime.Literal)solution.getValue(var);
                results.add(lit);
            }
        } catch (xowl.interpreter.XOWLException ex) { }
        return results;
    }
    
    protected void removeAllObjects(xowl.lang.owl2.IRI property) {
        java.util.List<xowl.lang.runtime.Entity> values = getAllEntities(property);
        for (xowl.lang.runtime.Entity value : values) {
            xowl.lang.owl2.ObjectPropertyAssertion axiom = new xowl.lang.owl2.ObjectPropertyAssertion();
            axiom.setObjectProperty(property);
            axiom.setIndividual(entity.getHasIRI());
            axiom.setValueIndividual(value.getHasIRI());
            removeAxiom(axiom);
        }
    }
    
    protected void removeAllData(xowl.lang.owl2.IRI property) {
        java.util.List<xowl.lang.runtime.Literal> values = getAllDataAsLiterals(property);
        for (xowl.lang.runtime.Literal value : values) {
            xowl.lang.owl2.Literal lit = new xowl.lang.owl2.Literal();
            lit.setLexicalValue(value.getLexicalValue());
            lit.setMemberOf(value.getMemberOf().getInterpretationOf().getHasIRI());
            xowl.lang.owl2.DataPropertyAssertion axiom = new xowl.lang.owl2.DataPropertyAssertion();
            axiom.setDataProperty(property);
            axiom.setIndividual(entity.getHasIRI());
            axiom.setValueLiteral(lit);
            removeAxiom(axiom);
        }
    }

    protected xowl.lang.owl2.ObjectPropertyAssertion createObjectAxiom(xowl.lang.owl2.IRI property, xowl.lang.runtime.Entity value) {
        xowl.lang.owl2.ObjectPropertyAssertion axiom = new xowl.lang.owl2.ObjectPropertyAssertion();
        axiom.setObjectProperty(property);
        axiom.setIndividual(entity.getHasIRI());
        axiom.setValueIndividual(value.getHasIRI());
        return axiom;
    }

    protected xowl.lang.owl2.ObjectPropertyAssertion createObjectAxiom(xowl.lang.owl2.IRI property, java.lang.Class _class) {
        String iri = null;
        try {
            java.lang.reflect.Method method = _class.getMethod("getIRI");
            iri = (String)method.invoke(null);
        } catch (java.lang.Exception ex) { return null; }
        xowl.lang.runtime.Entity value = repository.resolveEntity(iri);
        return createObjectAxiom(property, value);
    }

    protected xowl.lang.owl2.DataPropertyAssertion createDataAxiom(xowl.lang.owl2.IRI property, Object value) {
        xowl.lang.owl2.DataPropertyAssertion axiom = new xowl.lang.owl2.DataPropertyAssertion();
        axiom.setDataProperty(property);
        axiom.setIndividual(entity.getHasIRI());
        axiom.setValueLiteral(getLiteral(value));
        return axiom;
    }

    public xowl.lang.owl2.Literal getLiteral(Object obj) {
        xowl.interpreter.impl.JavaDatatype datatype = xowl.interpreter.impl.JavaDatatype.get(obj.getClass());
        xowl.lang.owl2.Literal lit = new xowl.lang.owl2.Literal();
        lit.setLexicalValue(datatype.getToString(obj));
        lit.setMemberOf(repository.getIRI(datatype.getOWLDatatype()));
        return lit;
    }

    protected xowl.lang.runtime.Entity getEntityFor(Object obj) {
        return ((ProxyObject)obj).getEntity();
    }

    protected ProxyObject getObjectFor(xowl.lang.runtime.Entity entity) {
        ProxyObject bpo = database.get(entity);
        if (bpo != null) return bpo;
        java.util.List<xowl.lang.runtime.Entity> classes = new java.util.ArrayList<xowl.lang.runtime.Entity>();
        xowl.lang.actions.QueryVariable var = new xowl.lang.actions.QueryVariable();
        var.setName("c");
        xowl.lang.owl2.ClassAssertion axiom = new xowl.lang.owl2.ClassAssertion();
        axiom.setIndividual(entity.getHasIRI());
        axiom.setClasse(var);
        xowl.lang.actions.ValuesOf query = new xowl.lang.actions.ValuesOf();
        query.addAxioms(axiom);
        query.setOn(var);
        try {
            Collection<xowl.interpreter.QuerySolution> solutions = repository.select(query);
            for (xowl.interpreter.QuerySolution solution : solutions)
                classes.add((xowl.lang.runtime.Entity)solution.getValue(var));
        } catch (xowl.interpreter.XOWLException ex) { }
        for (xowl.lang.runtime.Entity c : classes) {
            String iri = c.getHasIRI().getHasValue();
            ProxyObjectGetter getter = mapIRI2Getter.get(iri);
            if (getter == null)
                continue;
            getter.get(repository, entity);
        }
        return null;
    }

    protected java.lang.Class getClassFor(xowl.lang.runtime.Entity entity) {
        String iri = entity.getHasIRI().getHasValue();
        return mapIRI2Class.get(iri);
    }

    protected boolean addAxiom(xowl.lang.owl2.Axiom axiom) {
        try {
            repository.apply(new xowl.interpreter.Change(ontology, axiom, true));
            return true;
        }
        catch (xowl.interpreter.XOWLException ex) { return false; }
    }
    protected boolean removeAxiom(xowl.lang.owl2.Axiom axiom) {
        try {
            repository.apply(new xowl.interpreter.Change(ontology, axiom, false));
            return true;
        }
        catch (xowl.interpreter.XOWLException ex) { return false; }
    }
    
    public abstract xowl.lang.runtime.ObjectProperty getObjectProperty(String name);
    public abstract xowl.lang.runtime.DataProperty getDataProperty(String name);

    @Override public ProxyObject getObjectValue(String name) { return getObjectValue(getObjectProperty(name).getInterpretationOf().getHasIRI()); }
    @Override public Object getDataValue(String name) { return getDataValue(getObjectProperty(name).getInterpretationOf().getHasIRI()); }
    @Override public Collection<ProxyObject> getObjectValues(String name) { return getObjectValues(getObjectProperty(name).getInterpretationOf().getHasIRI()); }
    @Override public Collection<Object> getDataValues(String name) { return getDataValues(getObjectProperty(name).getInterpretationOf().getHasIRI()); }
    @Override public ProxyObject getObjectValue(xowl.lang.owl2.IRI property) {
        Collection<ProxyObject> temp = getAllObjects(property);
        if (temp.isEmpty()) return null;
        return temp.iterator().next();
    }
    @Override public Object getDataValue(xowl.lang.owl2.IRI property) {
        Collection<Object> temp = getAllData(property);
        if (temp.isEmpty()) return null;
        return temp.iterator().next();
    }
    @Override public Collection<ProxyObject> getObjectValues(xowl.lang.owl2.IRI property) { return getAllObjects(property); }
    @Override public Collection<Object> getDataValues(xowl.lang.owl2.IRI property) { return getAllData(property); }
    
    @Override public void addPropertyObserver(String name, ObjectPropertyObserver observer) {
        xowl.lang.runtime.ObjectProperty property = getObjectProperty(name);
        ObjectPropertyObserverLink link = new ObjectPropertyObserverLink(this, property, observer);
        repository.addObserver(link);
        mapObjObservers.put(observer, link);
    }

    @Override public void addPropertyObserver(String name, DataPropertyObserver observer) {
        xowl.lang.runtime.DataProperty property = getDataProperty(name);
        DataPropertyObserverLink link = new DataPropertyObserverLink(this, property, observer);
        repository.addObserver(link);
        mapDataObservers.put(observer, link);
    }

    @Override public void addPropertyObserver(xowl.lang.owl2.IRI property, ObjectPropertyObserver observer) {
        xowl.lang.runtime.ObjectProperty prop = repository.interpretAsObjectProperty(repository.resolveEntity(property));
        ObjectPropertyObserverLink link = new ObjectPropertyObserverLink(this, prop, observer);
        repository.addObserver(link);
        mapObjObservers.put(observer, link);
    }
    
    @Override public void addPropertyObserver(xowl.lang.owl2.IRI property, DataPropertyObserver observer) {
        xowl.lang.runtime.DataProperty prop = repository.interpretAsDataProperty(repository.resolveEntity(property));
        DataPropertyObserverLink link = new DataPropertyObserverLink(this, prop, observer);
        repository.addObserver(link);
        mapDataObservers.put(observer, link);
    }
    
    @Override public void removePropertyObserver(ObjectPropertyObserver observer) {
        ObjectPropertyObserverLink link = mapObjObservers.get(observer);
        repository.removeObserver(link);
        mapObjObservers.remove(observer);
    }

    @Override public void removePropertyObserver(DataPropertyObserver observer) {
        DataPropertyObserverLink link = mapDataObservers.get(observer);
        repository.removeObserver(link);
        mapDataObservers.remove(observer);
    }
    
    @Override public void removeAllObservers() {
        Set<ObjectPropertyObserver> objObs = new HashSet<ObjectPropertyObserver>(mapObjObservers.keySet());
        for (ObjectPropertyObserver observer : objObs) {
            ObjectPropertyObserverLink link = mapObjObservers.get(observer);
            repository.removeObserver(link);
            mapObjObservers.remove(observer);
        }
        Set<DataPropertyObserver> dataObs = new HashSet<DataPropertyObserver>(mapDataObservers.keySet());
        for (DataPropertyObserver observer : dataObs) {
            DataPropertyObserverLink link = mapDataObservers.get(observer);
            repository.removeObserver(link);
            mapDataObservers.remove(observer);
        }
    }
}
